#ifndef _GRO_GUI_H__
#define _GRO_GUI_H__

#include <list>
#include <map>
#include <string>

#include "SDL.h"

#include "GRO_Renderer.h"
#include "GRO_Font.h"

#include "GRO_Painter.h"
#include "GRO_Beacons.h"

#include "GRO_Input.h"

#include "tinyxml.h"

namespace GRO
{
    
class Gui;
class Panel;
class Group;

struct Point
{
    public:
    int x, y;
    
    Point()
        : x(0), y(0)
    {}
    Point(int x, int y)
        : x(x), y(y)
    {}
    
    bool operator==(const Point& A)
    {
        return (x == A.x && y == A.y);
    }
    bool operator!=(const Point& A)
    {
        return (x != A.x || y != A.y);
    }
    Point operator+(const Point& A)
    {
        return Point(x + A.x, y + A.y);
    }
    
    std::string toString() const
    {
        char buf[30];
        snprintf(buf, 30, "[%d, %d]", x, y);
        return buf;
    }
};

struct Dimensions
{
    public:
    unsigned int w, h;
    
    Dimensions()
        : w(0), h(0)
    {}
    Dimensions(unsigned int w, unsigned int h)
        : w(w), h(h)
    {}
    
    bool operator==(const Dimensions& A)
    {
        return (w == A.w && h == A.h);
    }
    bool operator!=(const Dimensions& A)
    {
        return (w != A.w || h != A.h);
    }
    
    std::string toString() const
    {
        char buf[30];
        snprintf(buf, 30, "[%d, %d]", w, h);
        return buf;
    }
};


inline bool InRect(int x, int y, const Point& p, const Dimensions& d)
{
    return (p.x <= x && x <= int(p.x + d.w) && p.y <= y && y <= int(p.y + d.h));
}

inline SDL_Rect MakeRect(Sint16 x, Sint16 y, Uint16 w, Uint16 h)
{
    SDL_Rect r = {x, y, w, h};
    return r;
}



struct GRO_WidgetID
{
    private:
    unsigned int id;
    
    public:
    
    GRO_WidgetID()
        : id(0)
    {}
    
    GRO_WidgetID(unsigned int id)
        : id(id)
    {}
    
    void setValue(unsigned int id)
    {
        this->id = id;
    }
    
    unsigned int getValue() const
    {
        return id;
    }
    
    bool operator<(const GRO_WidgetID& ID) const
    {
        return (id < ID.id);
    }
    
    bool operator==(const GRO_WidgetID& ID) const
    {
        return (id == ID.id);
    }
};


class WidgetMap
{
    private:
    friend class Gui;
    
    std::set<Widget*> widgets;
    std::map<std::string, Widget*> names;
    std::map<GRO_WidgetID, Widget*> ids;
    
    void add(Widget* w);
    void add(const std::string& name);
    void add(const GRO_WidgetID& id);
    void remove(Widget* w);
    void remove(const std::string& name);
    void remove(const GRO_WidgetID& id);
    
    public:
    
    unsigned int size() const;
    
    Widget* get(const std::string& name);
    Widget* get(const GRO_WidgetID& id);
};


// FIXME: Convert this to #defines so that custom widgets can be added
    // on C++0x builds.
    enum TypeEnum
    {
        CUSTOM, // User-defined widget
        GROUP, // Holds other widgets
        SEPARATOR, // Separates widgets with a drawn line
        EXPANDER, // Holds an empty space in a group
        MOVER, // Provides user access to moving a widget (like a window)
        RESIZER, //
        PANEL, // A basic window: Is a group
        CHECKBOX, // On/off button
        TEXT, // One or many lines of text
        BUTTON, // Can be toggled, open/close panels, or run a callback
        SLIDER, // Changes an integer by sliding a handle along a track
        IMAGE, // Displays a picture
        PROGRESSBAR, // An expanding bar to indicate progress
        RADIO, // On/off button, can connect to others through RadioSystem
        COLOR, // A box of color
        GRADIENT, // A box of fading colors
        COLORPICKERSIMPLE, // Uses sliders to choose a color
        SCROLLBAR, // A slider that scrolls other widgets
        SELECTTEXT, // A selectable text
        LISTBOX, // A list of selectable texts
        SPINNER, // A value controlled by two buttons
        MENU, // A list of clickable or hoverable choices
        DROPDOWN, // Choose text from a popup list
        FILEBROWSER, // Uses selectable text to choose a file
        WINDOW // A fancy panel
    };
    
    inline std::string getTypeString(TypeEnum type)
    {
        switch(type)
        {
            case CUSTOM:
                return "Custom";
            case GROUP:
                return "Group";
            case SEPARATOR:
                return "Separator";
            case EXPANDER:
                return "Expander";
            case MOVER:
                return "Mover";
            case RESIZER:
                return "Resizer";
            case PANEL:
                return "Panel";
            case CHECKBOX:
                return "CheckBox";
            case TEXT:
                return "Text";
            case BUTTON:
                return "Button";
            case SLIDER:
                return "Slider";
            case IMAGE:
                return "Image";
            case PROGRESSBAR:
                return "ProgressBar";
            case RADIO:
                return "Radio";
            case COLOR:
                return "Color";
            case GRADIENT:
                return "Gradient";
            case COLORPICKERSIMPLE:
                return "ColorPickerSimple";
            case SCROLLBAR:
                return "ScrollBar";
            case SELECTTEXT:
                return "SelectText";
            case LISTBOX:
                return "ListBox";
            case SPINNER:
                return "Spinner";
            case MENU:
                return "Menu";
            case DROPDOWN:
                return "Dropdown";
            case FILEBROWSER:
                return "FileBrowser";
            case WINDOW:
                return "Window";
            default:
                return "Unknown type";
        }
    }

class Widget
{
    private:
    friend class Gui;
    static unsigned int lastID;
    public:
    
    protected:
        TypeEnum _type;
        Gui* _gui; // Take default stuff from gui
        Panel* _panel;
        Group* _group; // clip drawing to the parent group
        Widget* _parent;


        Uint8 _depth;  // z-coord.  Bigger is on top.
        bool _hidden; // don't draw widget (but still hold a spot in the grid)
        bool _folded; // don't draw and don't take up space
        
        void setChild(Widget* child)
        {
            if(child != NULL)
                child->_parent = this;
        }


    public:
    
        PainterSet painters;
    
        Beacon onPress;
        Beacon onRelease;
        Beacon onClick;
        Beacon onDoubleClick;
        Beacon onScrollUp;
        Beacon onScrollDown;
        Beacon onMotion;
        Beacon onDrag;
        Beacon onHover;
        Beacon onEnter;
        Beacon onLeave;

        std::string name;
        GRO_WidgetID id;

        bool grabScrollFocus;
        bool grabKeyboardFocus;

        /*
        bool pinned;
        // Relative (%) coords for positioning and sizing according to the group's dimensions
        RelRect* relPinCoords;
        SDL_Rect* relPinCoordsOffset;
        bool scrollable;

        Rect padding;  // Spacing for the sides of the widget away from other widgets and panel sides
        */
        
        Point pos;
        Dimensions dims;
        //SDL_Rect area; // position and drawing area for dirty rect reporting and click hits
        Rect* designPush;
        


        Widget(TypeEnum Type, const Dimensions& dims);
        virtual ~Widget()
        {
            delete designPush;
            /*delete relPinCoords;
            delete relPinCoordsOffset;*/
        }

        TypeEnum getType()
        {
            return _type;
        }
        
        void setName(const std::string& name)
        {
            setGui(NULL);
            this->name = name;
            setGui(_gui);
        }

        bool isHidden()
        {
            return _hidden;
        }
        void hide(bool enable)
        {
            _hidden = enable;
        }
        void hide()
        {
            _hidden = true;
        }
        void unhide()
        {
            _hidden = false;
        }
        void toggleHidden()
        {
            _hidden = !_hidden;
        }
        
        bool isFolded()
        {
            return _folded;
        }
        void fold(bool fold)
        {
            _folded = fold;
        }
        void fold()
        {
            _folded = true;
        }
        void unfold()
        {
            _folded = false;
        }
        void toggleFolded()
        {
            _folded = !_folded;
        }
        void setDepth(Uint8 Depth = 127);
        Uint8 getDepth()
        {
            return _depth;
        }
        
        virtual void draw(SDL_Rect* clipRect);
        // Use gui to get time and dt.
        virtual void update()
        {}

        //virtual void updateRelCoords();

        // Window movement
        virtual void move(int x, int y, bool relative = true);

        virtual void resize(unsigned int w, unsigned int h)
        {
            dims.w = w;
            dims.h = h;
        }

        virtual Widget* action(PointerCursor* mouse, ActionEnum actionType);
        virtual Widget* action(NavCursor* nav, ActionEnum actionType);

        virtual Widget* scroll(DirectionEnum direction, float numIncrements)
        {
            return NULL;
        }

        virtual Widget* findWidget(int mx, int my);
        
        virtual std::list<Widget*> getChildren()
        {
            return std::list<Widget*>();
        }

        virtual void keyboardEvent(SDL_Event& event)
        {}

        Gui* getGui()
        {
            return _gui;
        }
        virtual void setGui(Gui* Gui);

        Panel* getPanel()
        {
            return _panel;
        }
        virtual void setPanel(Panel* Panel)
        {
            _panel = Panel;
        }

        Group* getGroup()
        {
            return _group;
        }
        virtual void setGroup(Group* Group)
        {
            _group = Group;
        }
        
        Widget* getParent()
        {
            return _parent;
        }
        
        void connectByName(const std::string& widget, const std::string& beacon, const std::string& fn_name, const std::vector<std::string>& args);
        void connectByWidget(Widget* widget, const std::string& beacon, const std::string& fn_name, const std::vector<std::string>& args);
        
        virtual void connectBeacon(Widget* widget, Beacon* beacon, const std::string& fn_name, const std::vector<std::string>& args);
        
        virtual TiXmlElement* save(TiXmlElement* parent)
        {
            TiXmlElement* w = new TiXmlElement("Widget");
            parent->LinkEndChild(w);
            
            saveWidgetAttributes(w);
            
            return w;
        }
        
        void saveWidgetAttributes(TiXmlElement* w);
        
        virtual bool load(TiXmlElement* w)
        {
            return loadWidgetAttributes(w);
        }
        
        bool loadWidgetAttributes(TiXmlElement* w);
};







class Cursor;


class Shortcut
{
public:
    Uint8 numMods;
    Uint16 mod[3];
    SDLKey key;
    Beacon onExecute;

    Shortcut(Uint16 modifier, SDLKey key)
            : numMods(0)
            , key(key)
    {
        init(modifier);
    }
    
    ~Shortcut()
    {}

    void init(Uint16 modifier)
    {
        mod[0] = mod[1] = mod[2] = 0;
        Uint16 test;
        test = KMOD_SHIFT & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        test = KMOD_CTRL & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        test = KMOD_ALT & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        if (numMods >= 3)
            return;
        test = KMOD_META & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        if (numMods >= 3)
            return;
        test = KMOD_NUM & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        if (numMods >= 3)
            return;
        test = KMOD_CAPS & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        if (numMods >= 3)
            return;
        test = KMOD_MODE & modifier;
        if (test)
        {
            mod[numMods] = test;
            numMods++;
        }
        if (numMods >= 3)
            return;

    }

    bool event(SDL_keysym& keysym)
    {
        if (numMods > 3 || keysym.sym != key)
            return false;
            
        if(numMods == 0 && keysym.mod != KMOD_NONE)
            return false;
        
        for (int i = 0; i < numMods; i++)
        {
            if (!(mod[i] & keysym.mod))
                return false;  // Short-circuit the testing (logical AND for each necessary mod)
        }
        
        onExecute.emit();
        return true;
    }


};








    

class Gui
{
    private:
    Renderer* renderer;
    
    class RegisteredBeacon
    {
        public:
        std::string beacon;
        std::string widget;
        Widget* widgetPtr;
        std::string fn_widget;
        Widget* fn_widgetPtr;
        std::string fn;
        std::vector<std::string> args;
        
        RegisteredBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name)
            : beacon(beaconName)
            , widget(widgetName)
            , widgetPtr(NULL)
            , fn_widget(fn_widget)
            , fn_widgetPtr(NULL)
            , fn(fn_name)
        {}
        RegisteredBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1)
            : beacon(beaconName)
            , widget(widgetName)
            , widgetPtr(NULL)
            , fn_widget(fn_widget)
            , fn_widgetPtr(NULL)
            , fn(fn_name)
        {
            args.push_back(arg1);
        }
        RegisteredBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1, const std::string& arg2)
            : beacon(beaconName)
            , widget(widgetName)
            , widgetPtr(NULL)
            , fn_widget(fn_widget)
            , fn_widgetPtr(NULL)
            , fn(fn_name)
        {
            args.push_back(arg1);
            args.push_back(arg2);
        }
    };
    
    
    public:
    std::list<RegisteredBeacon> registeredBeacons;
    std::map<Widget*, TiXmlElement*> widgetToXML;
    
    Point pos;
    Dimensions dims;
    
    Widget* cursorFocus; // Tell this one about text input events
    
    float dt;  // in seconds
    Uint32 time;  // in milliseconds
    
    
    std::list<Panel*> panels;
    
    std::list<Cursor*> cursors;
    std::list<Shortcut*> shortcuts;
    
    WidgetMap widgets;
    
    Gui();
    ~Gui();
    
    void init(SDL_PixelFormat* format, unsigned int width, unsigned int height);
    
    Panel* add(Panel* panel);
    void registerWidget(Widget* widget);
    void unregisterWidget(Widget* widget);
    
    void registerBeacon(Widget* widget, const std::string& beaconName, Widget* fn_widget, const std::string& fn_name);
    void registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name);
    void registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1);
    void registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1, const std::string& arg2);
    
    void saveBeacon(TiXmlElement* w, Gui* gui, const std::string& name, const std::string& beaconName);

    void setRenderer(Renderer* render);
    void freeRenderer();
    Renderer* getRenderer() const;
    
    
    void addShortcut(Shortcut* shortcut);
    

    Widget* findWidget(int mx, int my, bool allowGrabbing = false, bool setPanelFocus = false);
    Panel* getPanel(Sint16 mx, Sint16 my);
    

    void draw();

    void update();
    Widget* event(SDL_Event& Event);
    
    
    bool save(const std::string& file);
    bool load(const std::string& file);
};


}



#include "GRO_Input.h"




#endif
